package pbbltobpel;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;

import pbbltobpel.exceptions.PartnerLinkMissing;
import pbbltobpel.model.Import;
import pbbltobpel.model.PartnerLinkMyRole;
import pbbltobpel.model.PartnerLinkPartnerRole;
import pbbltobpel.model.Process;
import pbbltobpel.model.Variable;
import pbbltobpel.model.steps.AssignStep;
import pbbltobpel.model.steps.Copy;
import pbbltobpel.model.steps.CopyParameter;
import pbbltobpel.model.steps.InvokeStep;
import pbbltobpel.model.steps.ReceiveStep;
import pbbltobpel.model.steps.ReplyStep;
import pbbltobpel.parser.PddlPlan;
import pbbltobpel.parser.PlanStep;

import com.predic8.wsdl.Binding;
import com.predic8.wsdl.BindingOperation;
import com.predic8.wsdl.Definitions;
import com.predic8.wsdl.Message;
import com.predic8.wsdl.Operation;
import com.predic8.wsdl.Part;
import com.predic8.wsdl.PortType;
import com.predic8.wsdl.Service;

class VariablePart
{
	public Variable variable;
	public String partName;
}

public class Converter {
	

	public static String getNamespace(String param)
	{
		int pos = param.indexOf("#");
		String tmp = param.substring(0, pos);
		return tmp;
	}
	
	public static Process Convert(PddlPlan plan, ParameterTypeMapper mapper) throws PartnerLinkMissing
	{
		Process process = new Process();
		process.setName(plan.getFileName());
		process.setTargetNamespace("http://" + plan.getFileName());
		process.setSuppressJoinFailure(true);
		process.setExitOnStandardFault(true);
		process.getNamespaces().put("tns", process.getTargetNamespace());
		
		Import imp = new Import();
		imp.setLocation(plan.getFileName() + ".wsdl");
		imp.setNamespace(process.getTargetNamespace());
		imp.setImportType("http://schemas.xmlsoap.org/wsdl/");
		process.getImports().add(imp);
		
		PartnerLinkMyRole clientPartnerLink = new PartnerLinkMyRole();
		clientPartnerLink.setName("client");
		clientPartnerLink.setMyRole(plan.getFileName() + "Provider");
		clientPartnerLink.setPartnerLinkType("tns:" + plan.getFileName());
		process.getPartnerLinks().add(clientPartnerLink);
		
		Variable inputVariable = new Variable();
		inputVariable.setName("input");
		inputVariable.setMessageType("tns:" + plan.getFileName() + "RequestMessage");
		process.getVariables().add(inputVariable);
		
		Variable outputVaribale = new Variable();
		outputVaribale.setName("output");
		outputVaribale.setMessageType("tns:" + plan.getFileName() + "ResponseMessage");
		process.getVariables().add(outputVaribale);
		
		ReceiveStep receiveStep = new ReceiveStep();
		receiveStep.setCreateInstance(true);
		receiveStep.setLink(clientPartnerLink);
		receiveStep.setName("start");
		receiveStep.setOperation("process");
		receiveStep.setVariable(inputVariable);
		
		process.getSteps().add(receiveStep);
//		
		int nsnumber = 1;
		Hashtable<String, Definitions> wsdls = plan.getWsdls();
		Hashtable<String, PartnerLinkPartnerRole> partnerLinks = new Hashtable<String, PartnerLinkPartnerRole>();
		Hashtable<String, Hashtable<String, String> > serviceMessages = new Hashtable<String, Hashtable<String, String> >();

		Hashtable<String, String> namespacesPrefixes = new Hashtable<String, String>();
		Hashtable<String, Hashtable<String, List<String> > > messagePartsTypes 
										= new Hashtable<String, Hashtable<String,List<String>>>();

		Hashtable<String, String> operationsNames = new Hashtable<String, String>();
		Hashtable<String, String> portTypes = new Hashtable<String, String>();
		
		for(Definitions def : wsdls.values())
		{
			Hashtable<String, Hashtable<String, Hashtable<String, String> > > ports = new Hashtable<String, Hashtable<String, Hashtable<String, String> > >();
			
			for (Message m : def.getMessages()) 
			{
				Hashtable<String, List<String> > partTypes = new Hashtable<String, List<String> >();
				for (Part part : m.getParts())
				{
					String type = null;
					if (part.getType() != null)
					{
						type = "";
						if (!part.getType().getPrefix().isEmpty())
							type = part.getType().getPrefix()+":";
						type += part.getType().getLocalPart();
					} 
					else if (part.getElement() != null && !part.getElement().isEmpty())
					{
						type = part.getElement();
					}
					
					if (!partTypes.containsKey(type))
						partTypes.put(type, new ArrayList<String>());
					
					
					List<String> list = partTypes.get(type);
					list.add(part.getName());
					
					partTypes.put(type, list);
				}
				messagePartsTypes.put(m.getName(), partTypes);
			}
		
			
			for(PortType p : def.getPortTypes())
			{
				Hashtable<String, Hashtable<String, String> > operations = new Hashtable<String, Hashtable<String, String> >();
				for (Operation o : p.getOperations())
				{
					Hashtable<String, String> messages = new Hashtable<String, String> ();

					messages.put(o.getInput().getName(), o.getInput().getMessage().getName());
					messages.put(o.getOutput().getName(), o.getOutput().getMessage().getName());

					operations.put(o.getName(), messages);
				}
				ports.put(p.getName(), operations);
			}

			Hashtable<String, String> bindingOperations = new Hashtable<String, String>();			
			Hashtable<String, String> bindingPorts = new Hashtable<String, String>();			

			Hashtable<String, Hashtable<String, String> > bindingVariables = new Hashtable<String, Hashtable<String,String>>();
			for(Binding b : def.getBindings())
			{
				if (b.getOperations().isEmpty())
					continue;
				BindingOperation o = b.getOperations().get(0);

				bindingOperations.put(b.getName(), o.getName());
				bindingPorts.put(b.getName(), b.getType().getLocalPart());
				
				Hashtable<String, String> messages = new Hashtable<String, String>();
				messages.put("input", ports.get(b.getType().getLocalPart()).get(o.getName()).get(o.getInput().getName()));
				messages.put("output", ports.get(b.getType().getLocalPart()).get(o.getName()).get(o.getOutput().getName()));
				
				bindingVariables.put(b.getName(), messages);
			}
			
			for(Service s : def.getServices())
			{
				PartnerLinkPartnerRole partner = new PartnerLinkPartnerRole();
				partner.setName(s.getName());
				partner.setPartnerRole(s.getName() + "Role");
				partner.setPartnerLinkType("tns:" + s.getName() + "LinkType");
				process.getPartnerLinks().add(partner);
				partnerLinks.put(s.getName(), partner);

				String prefix = "ns" + nsnumber++;
				namespacesPrefixes.put(s.getName(), prefix);
				
				process.getNamespaces().put(prefix, def.getTargetNamespace());
				if(s.getPorts().isEmpty())
					continue;
				Hashtable<String, String> messages = bindingVariables.get(s.getPorts().get(0).getBinding().getName());
				serviceMessages.put(s.getName().trim(), messages);
				
				operationsNames.put(s.getName().trim(), bindingOperations.get(s.getPorts().get(0).getBinding().getName()));
				portTypes.put(s.getName().trim(), bindingPorts.get(s.getPorts().get(0).getBinding().getName()));
			}
			
		}
		
		
		Hashtable<String, String> nddic = new Hashtable<String, String>();
		for(PlanStep step : plan.getSteps())
		{
			for(String param : step.getParams())
			{
				String ns = getNamespace(param);
				if(!nddic.containsKey(ns))
				{
					nddic.put(ns, "ns" + nsnumber++);
				}
			}
		}
		for(String key : nddic.keySet())
		{
			process.getNamespaces().put(nddic.get(key), key);
		}
		
		
		int c = 0;
		Variable inVar = null;
		Variable outVar = null;
		Hashtable<String, VariablePart> newVariables = new Hashtable<String, VariablePart>();
		for(PlanStep step : plan.getSteps())
		{
			InvokeStep invoke = new InvokeStep();
			invoke.setName("invoke" + step.getNumber());
			String service = step.getName().substring(1 + step.getName().indexOf("#"));
			if(!partnerLinks.containsKey(service))
				throw new PartnerLinkMissing(service);

			String namespacePrefix = namespacesPrefixes.get(service); 
			
			invoke.setLink(partnerLinks.get(service));
			
			invoke.setOperation(operationsNames.get(service));
			invoke.setPortType(namespacePrefix + ":" + portTypes.get(service));

			
			inVar = new Variable();
			inVar.setName(service + "Request");
			inVar.setMessageType(namespacePrefix+":"+serviceMessages.get(service).get("input"));

			outVar = new Variable();
			outVar.setName(service + "Response");
			outVar.setMessageType(namespacePrefix+":"+serviceMessages.get(service).get("output"));
			
			
			invoke.setInputVariable(inVar);
			invoke.setOutputVariable(outVar);
			
			if (!process.getVariables().contains(inVar))
				process.getVariables().add(inVar);

			if (!process.getVariables().contains(outVar))
				process.getVariables().add(outVar);

			AssignStep	assignStep1 = new AssignStep();
			assignStep1.setName("assign" + ++c);
			assignStep1.setValidate(false);
			List<Copy> copies = new ArrayList<Copy>();

			//////
			List<String> params = step.getParams();
			HashSet<String> added = new HashSet<String>();
			for (String param : params)
			{
				String partName = param.substring(1 + param.indexOf("#"));
				String partType = mapper.getParamType(partName); 

				// input
				// kopiuj z wejścia
				if (param.contains("Ontology1.owl#"))
				{
					String messageName = inVar.getMessageType().substring(1+inVar.getMessageType().indexOf(":"));
					
					if (added.contains(messageName + " " + partType + " " + partName))
						continue;

					added.add(messageName + " " + partType + " " + partName);
					
					for (String part : messagePartsTypes.get(messageName).get(partType))
					{					
						Copy cop = new Copy();
						
						CopyParameter from = new CopyParameter();
						from.setPart(partName);
						from.setVariable(inputVariable);
						cop.setFrom(from);

						CopyParameter to = new CopyParameter();
						to.setPart(part);
						to.setVariable(inVar);

						cop.setTo(to);
						
						copies.add(cop);
					}
				}
				// nowa lub istniejąca zmienna, jeśli pierwszy raz wystepuje tzn wyjście, jeśli nie to paramter
				// i kopiujemy
				else if (param.contains("Ontology2.owl#"))
				{
					if (newVariables.containsKey(partName))
					{
						Variable v = newVariables.get(partName).variable;
						String p = newVariables.get(partName).partName;
						String messageName = v.getMessageType().substring(1+v.getMessageType().indexOf(":"));

						if (added.contains(messageName + " " + partType + " " + partName))
							continue;

						added.add(messageName + " " + partType + " " + partName);
						
						for (String part : messagePartsTypes.get(messageName).get(partType))
						{						
							Copy cop = new Copy();
							
							CopyParameter from = new CopyParameter();
							from.setPart(p);
							from.setVariable(v);
							cop.setFrom(from);
	
							CopyParameter to = new CopyParameter();
							to.setPart(part);
							to.setVariable(inVar);
	
							cop.setTo(to);
							
							copies.add(cop);
						}
					}
					else
					{
						String messageName = outVar.getMessageType().substring(1+outVar.getMessageType().indexOf(":"));

						if (added.contains(messageName + " " + partType + " " + partName))
							continue;

						added.add(messageName + " " + partType + " " + partName);
						
						for (String part : messagePartsTypes.get(messageName).get(partType))
						{						
							VariablePart vp = new VariablePart();
							vp.variable = outVar;
							vp.partName = part;
							
							newVariables.put(partName, vp);
						}
					}

				}
			}

			assignStep1.setCopies(copies);			
			process.getSteps().add(assignStep1);						

			process.getSteps().add(invoke);
		}

		AssignStep	assignStep1 = new AssignStep();
		assignStep1.setName("assign" + ++c);
		assignStep1.setValidate(false);
		List<Copy> copies = new ArrayList<Copy>();
		
		String messageName = outVar.getMessageType().substring(1+outVar.getMessageType().indexOf(":"));
		Hashtable<String, List<String> > outputMessage = messagePartsTypes.get(messageName);
		for (List<String> keys : outputMessage.values())
		{
			for (String key : keys)
			{
				Copy cop = new Copy();
				
				CopyParameter from = new CopyParameter();
				from.setPart(key);
				from.setVariable(outVar);
				CopyParameter to = new CopyParameter();
				to.setPart(key);
				to.setVariable(outputVaribale);
		
				cop.setFrom(from);
				cop.setTo(to);
				
				copies.add(cop);
			}
		}

		assignStep1.setCopies(copies);
		
		process.getSteps().add(assignStep1);
		
		ReplyStep  replyStep = new ReplyStep();
		replyStep.setLink(clientPartnerLink);
		replyStep.setName("stop");
		replyStep.setOperation("process");
		replyStep.setVariable(outputVaribale);
		
		process.getSteps().add(replyStep);

		
		return process;
	}
}
